package com.boe.hbd.client.persistence;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import com.boe.hbd.client.domain.BaseEntity;
import com.boe.hbd.client.utils.Assert;
import com.boe.hbd.client.utils.ChainedMap;

public class SQLiteAbatis extends SQLiteOpenHelper implements EntityManager {
	private final static String TAG = "SQLiteAbatis";
	private Context context;
	private String dbName;
	private SQLiteDatabase dbObj; 

	public SQLiteAbatis(Context context, String dbName) {
		super(context, dbName.concat(".db"), null, Integer.parseInt(getConfiguredParameter(context, dbName.concat("_abatis_version"))));
		this.dbName = dbName;
		this.context = context;
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		String initSQL = getConfiguredParameter(context, dbName.concat("_abatis_initSQL"));
		if (initSQL == null) {
			Log.e(TAG, "undefined sql id - " + dbName.concat("_abatis_initSQL"));
		} else {
			String[] sqls = initSQL.split(";");
			for (String sql : sqls) {
				try {
					db.execSQL(sql + ";");
				} catch (Throwable e) {
					Log.e(TAG , e.getMessage());
				}
			}
		}
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		onCreate(db);
	}

	private static String getConfiguredParameter(Context context, String paramName) {
		int identifier = context.getResources().getIdentifier(paramName, "string", context.getPackageName());
		return context.getResources().getString(identifier);
	}

	protected SQLiteDatabase getDbObject() {
		if (dbObj == null || !dbObj.isOpen()) {
			dbObj = getWritableDatabase();
		}
		return dbObj;
	}

	public <T extends BaseEntity> T create(T entityCreated) {
		Assert.notNull(entityCreated, "entityCreated can not be null");
		EntityMeta metaOfEntity = EntityMeta.getInstance(entityCreated.getClass());

		Long now = System.currentTimeMillis();
		if (entityCreated.getGmtCreated() == null) {
			entityCreated.setGmtCreated(now);
		}
		if (entityCreated.getGmtUpdated() == null) {
			entityCreated.setGmtUpdated(now);
		}
		try {
			long rowId = getDbObject().insert(metaOfEntity.getTableName(), null, metaOfEntity.toContentValues(entityCreated, false));
			if (rowId <= 0) {
				throw new SQLException("failed to insert row into " + metaOfEntity.getTableName());
			}
			entityCreated.setId(rowId);
			return entityCreated;
		} finally {
			if (dbObj != null ) dbObj.close();
		}
	}

	public <T extends BaseEntity> T update(T entityUpdated) {
		Assert.notNull(entityUpdated, "entityUpdated can not be null");
		EntityMeta metaOfEntity = EntityMeta.getInstance(entityUpdated.getClass());

		Long now = System.currentTimeMillis();
		if (entityUpdated.getGmtCreated() == null) {
			entityUpdated.setGmtCreated(now);
		}
		entityUpdated.setGmtUpdated(now);
		try {
			getDbObject().update(metaOfEntity.getTableName(), metaOfEntity.toContentValues(entityUpdated, true), BaseEntity.KEY_COLUMN + " = ? ",
			        new String[] { entityUpdated.getId().toString() });
			return entityUpdated;
		} finally {
			if (dbObj != null ) dbObj.close();
		}
	}

	public <T extends BaseEntity> int remove(T entityRemoved) {
		if (entityRemoved == null || entityRemoved.getId() == null) {
			return 0;
		}
		EntityMeta metaOfEntity = EntityMeta.getInstance(entityRemoved.getClass());
		try {
			return getDbObject().delete(metaOfEntity.getTableName(), BaseEntity.KEY_COLUMN + " = ? ", new String[] { entityRemoved.getId().toString() });
		} finally {
			if (dbObj != null ) dbObj.close();
		}
	}

	public <T extends BaseEntity> T getById(Class<? extends T> typeOfEntity, Long id) {
		EntityMeta metaOfEntity = EntityMeta.getInstance(typeOfEntity);
		String configuredSQL = "select * from " + metaOfEntity.getTableName() + " where _id = #id# ";
		List<T> entityList = queryBySQL(typeOfEntity, configuredSQL, new ChainedMap<String, Object>().chainedPut("id", id));
		if (entityList != null && entityList.size() > 0) {
			return entityList.get(0);
		}
		return null;
	}
	public <T extends BaseEntity> T getByName(Class<? extends T> typeOfEntity, String name) {
		EntityMeta metaOfEntity = EntityMeta.getInstance(typeOfEntity);
		String configuredSQL = "select * from " + metaOfEntity.getTableName() + " where name = #name# ";
		List<T> entityList = queryBySQL(typeOfEntity, configuredSQL, new ChainedMap<String, Object>().chainedPut("name", name));
		if (entityList != null && entityList.size() > 0) {
			return entityList.get(0);
		}
		return null;
	}
	public <T extends BaseEntity> List<T> query(Class<? extends T> typeOfEntity, String sqlId, ChainedMap<String, Object> params) {
		Assert.notNull(sqlId, "sqlId can not be null");
		String configuredSQL = getConfiguredParameter(context, sqlId);
		Assert.notNull(configuredSQL, "can not find SQL:" + sqlId);
		return queryBySQL(typeOfEntity, configuredSQL, params);
	}

	private <T extends BaseEntity> List<T> queryBySQL(Class<? extends T> typeOfEntity, String configuredSQL, ChainedMap<String, Object> params) {
		Assert.notNull(typeOfEntity, "typeOfEntity can not be null");

		if (params == null) {
			params = new ChainedMap<String, Object>();
		}
		// STEP 2: parse the SQL
		StringBuffer theSQL = new StringBuffer();
		List<String> bounds = new ArrayList<String>();
		for (int idx = 0, begin = -1, len = configuredSQL.length(); idx < len; idx++) {
			if (configuredSQL.charAt(idx) == '#') {
				if (begin < 0) {
					begin = idx;
				} else {
					bounds.add(configuredSQL.substring(begin + 1, idx));
					theSQL.append("?");
					begin = -1;
				}
			} else if (begin < 0) {
				theSQL.append(configuredSQL.charAt(idx));
			}
			if (idx == len - 1 && begin >= 0) {
				Assert.state(false, "undefined parameter:" + configuredSQL.substring(begin));
			}
		}
		// STEP 3: organize the bound parameters
		for (int idx = 0; idx < bounds.size(); idx++) {
			Assert.notNull(params.get(bounds.get(idx)), "can not find query parameter:" + bounds.get(idx));
			bounds.set(idx, params.get(bounds.get(idx)).toString());
		}
		// STEP 4: execute the query
		try {
			// TBD: using LazyList
			List<T> entityList = new ArrayList<T>();
			Cursor cursor = getDbObject().rawQuery(theSQL.toString(), bounds.toArray(new String[] {}));
			EntityMeta metaOfEntity = EntityMeta.getInstance(typeOfEntity);
			String[] columns = cursor.getColumnNames();
			while (cursor.moveToNext()) {
				Map<String, String> values = new HashMap<String, String>();
				for (int idx = 0; idx < columns.length; idx++) {
					if ( cursor.getString(idx) != null && !cursor.getString(idx).equals( "" )) {
					    values.put(columns[idx], metaOfEntity.getString(cursor, columns[idx], idx)); 
					}
				}
				T entity = metaOfEntity.<T> toEntity(values);
				if (entity != null) {
					entityList.add(entity);
				}
			}
			cursor.close();
			Log.i(TAG,theSQL.append(" returned:" + entityList.size() ).toString());
			return entityList;
		} finally {
			if (dbObj != null ) dbObj.close();
		}
	}

	private boolean initialized = false ;
	
	public boolean isInitialized() {
	    return initialized;
    }

	public void initialize(Context context) { 
		initialized = true ;
    }

	public void destroy(Context context) {
    }

	public void pause(Context context) {
    }

	public void resume(Context context) {
    }
}
